package net.kleinhenz.norabase.server.datasource;

import com.google.appengine.api.datastore.KeyFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import net.kleinhenz.norabase.client.datasource.BattlegroupListDSService;
import net.kleinhenz.norabase.server.BeanHelper;
import net.kleinhenz.norabase.server.EMF;
import net.kleinhenz.norabase.server.LoginServiceImpl;
import net.kleinhenz.norabase.server.model.Account;
import net.kleinhenz.norabase.shared.SortOrientation;
import net.kleinhenz.norabase.shared.TextMatchStyle;
import net.kleinhenz.norabase.shared.model.Battlegroup;
import net.kleinhenz.norabase.shared.model.FetchResult;
import net.kleinhenz.norabase.shared.model.Rune;

public class BattlegroupListDSServiceImpl extends RemoteServiceServlet implements BattlegroupListDSService {

	private static final long serialVersionUID = 1L;

	@SuppressWarnings("unchecked")
	@Override
	public FetchResult<Battlegroup<Rune>> fetch(final int start, final int end,
			final Map<String, SortOrientation> sortSpec,
			final TextMatchStyle textMatchStyle, 
			final Map<String, Object> criteria, String accountId) {

		List<Battlegroup<Rune>> result = new ArrayList<Battlegroup<Rune>>();
		EntityManager em = EMF.get().createEntityManager();

		List<net.kleinhenz.norabase.server.model.Battlegroup> dbResult = null;
		if (accountId==null || !accountId.equals(LoginServiceImpl.getUser())) {
			// return all public battlegroups
			Query q = em.createQuery("select from " + net.kleinhenz.norabase.server.model.Battlegroup.class.getName() + " where publicBattlegroup=:public");
			q.setParameter("public", Boolean.TRUE);
			dbResult = q.getResultList();
		} else {
			// return this users bgs
			Account account = retrieveAccount(em, accountId);
			dbResult = account.getBattlegroups();
		}

		for (net.kleinhenz.norabase.server.model.Battlegroup bgS : dbResult) {
			Battlegroup<Rune> b = new Battlegroup<Rune>();
			b.setAccountId(bgS.getAccountId());
			b.setDesc(bgS.getDesc());
			b.setStrat(bgS.getStrat());
			b.setName(bgS.getName());
			b.setPublicBattlegroup(bgS.isPublicBattlegroup());
			b.setId(KeyFactory.keyToString(bgS.getId()));
			result.add(b);
		}

		em.close();

		if (dbResult==null || dbResult.isEmpty())
			return new FetchResult<Battlegroup<Rune>>();
				
		// filtering
		List<Battlegroup<Rune>> toRemove = new ArrayList<Battlegroup<Rune>>();
		for (Battlegroup<Rune> c : result) {
			try {
				for (Map.Entry<String, Object> ce : criteria.entrySet()) {
					Object cVal = BeanHelper.getPropertyValue(c, ce.getKey());
					Object ceVal = ce.getValue();
					
					if (cVal instanceof Integer && ceVal instanceof Integer && ((Integer)cVal).intValue()!=((Integer)ceVal).intValue())
						toRemove.add(c);
					// FIXME Booolean cVal XOR ceVal -> remove
					else if (cVal instanceof String && ceVal instanceof String) {
						if (TextMatchStyle.EXACT.equals(textMatchStyle) && cVal!=null && !(((String)cVal).equals((String)ceVal)))
							toRemove.add(c);
						else if (TextMatchStyle.SUBSTRING.equals(textMatchStyle) && cVal!=null && !(((String)cVal).contains((String)ceVal)))
							toRemove.add(c);
					}
				}
			} catch (Exception e) {
				toRemove.add(c);
			}
		}
		result.removeAll(toRemove);

		// sorting
		if (sortSpec!=null)
			Collections.sort(result, new Comparator<Battlegroup<Rune>>() {
				@Override
				public int compare(Battlegroup<Rune> o1, Battlegroup<Rune> o2) {

				    /* Should return:
				     * o1>o2 1
				     * o1<o2 -1
				     * o1=o2 0
				     */
					int order = 0;
					for (Map.Entry<String, SortOrientation> se : sortSpec.entrySet()) {

						try {
							Object value1 = BeanHelper.getPropertyValue(o1, se.getKey());
							Object value2 = BeanHelper.getPropertyValue(o2, se.getKey());
							if (value1 instanceof Integer && value2 instanceof Integer) {
								if ((Integer)value1<(Integer)value2)
									order=-1;
								else if ((Integer)value2>(Integer)value1)
									order=1;
							} else 	if (value1 instanceof Double && value2 instanceof Double) {
								if ((Double)value1<(Double)value2)
									order=-1;
								else if ((Double)value2>(Double)value1)
									order=1;
	
							} else if (value1 instanceof String && value2 instanceof String) {
								order=((String)value2).compareTo((String)value1);
							}
							if (SortOrientation.DESCENDING.equals(se.getValue()))
								order*=-1;
						} catch (Exception e) {
							return 0;
						}
					}
					return order;
				}
			});

		// truncating - sublist not working, serialization problems
		FetchResult<Battlegroup<Rune>> finalResult = new FetchResult<Battlegroup<Rune>>();
		finalResult.fullSize = result.size();
		for (int n = start; n<(end>result.size()?result.size():end); n++)
				finalResult.add(result.get(n));
		return finalResult;
	}
	
	private Account retrieveAccount(EntityManager em, String accountId) {

		if (accountId==null || accountId.isEmpty())
			return null;

		Query q = em.createQuery("select from " + net.kleinhenz.norabase.server.model.Account.class.getName() + " where email=:email");
		q.setParameter("email", accountId);

		Object o = q.getSingleResult();
		if (o!=null)
			return (Account)o;
		else
			// can not happen, accounts are created on login implicitly
			throw new RuntimeException("Account not found: " + accountId);
	}
}
